package ldh.im.fxbase.service;

import javafx.scene.image.Image;
import ldh.im.dto.enums.ByteType;
import org.bytedeco.javacv.*;
import org.bytedeco.opencv.opencv_core.Mat;

import javax.sound.sampled.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

public class AvService {

    private static final AudioFormat.Encoding AUDIO_ENCODE = AudioFormat.Encoding.PCM_SIGNED;
    private final static int WEBCAM_DEVICE_INDEX = 0;
    private final static int captureWidth = 320;
    private final static int captureHeight = 260;
    private final static int fps = 25;

    private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
    private OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(WEBCAM_DEVICE_INDEX);
    private OpenCVFrameConverter<Mat> javaCVConv = new OpenCVFrameConverter.ToMat();
    private final JavaFXFrameConverter converter = new JavaFXFrameConverter();
    private byte[] fragment = null;
    private TargetDataLine targetDataLine;
    private boolean pause = false;

    private Consumer<AvData> consumer;

    public AvService(Consumer<AvData> consumer) {
        this.consumer = consumer;
    }

    public void start() throws Exception {
        grabber.setImageWidth(captureWidth);
        grabber.setImageHeight(captureHeight);
        grabber.start();

        startStream();

        initFragment();
        executorService.scheduleWithFixedDelay(buildVideoThread(), 0, 1000/fps, TimeUnit.MILLISECONDS);
        executorService.scheduleWithFixedDelay(buildAudioThread(), 0, 1000/fps, TimeUnit.MILLISECONDS);
    }

    private void startStream() throws Exception {
        AudioFormat audioFormat = getAudioFormat();
        DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat);
        targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo);
        while(targetDataLine == null) {
            Thread.sleep(10);
            targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo);
        }
        targetDataLine.open(audioFormat);
        targetDataLine.start();
    }

    public void close() throws Exception {
        if (targetDataLine != null) {
            targetDataLine.close();
        }
        if (grabber != null) {
            grabber.release();
            grabber.close();
        }
        executorService.shutdownNow();
    }

    private Runnable buildVideoThread() {
        return new Runnable() {

            @Override
            public void run() {
                try {
                    if(pause) return;

                    Frame frame = grabber.grab();
                    Image image = converter.convert(frame);

                    consumer.accept(new AvData(ByteType.av, image));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
    }

    private Runnable buildAudioThread() {
        return new Runnable() {

            @Override
            public void run() {
                try {
                    if(pause) return;

                    int length = targetDataLine.read(fragment, 0, fragment.length);
                    consumer.accept(new AvData(ByteType.audio, fragment, length));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
    }

    private AudioFormat getAudioFormat() {
        float sampleRate = 8000;     // 8000,11025,16000,22050,44100
        int sampleSizeInBits = 16;    // 8,16
        int channels = 1;             // 1,2
        int frameSize = 2;
        boolean signed = true;        // true,false
        boolean bigEndian = false;    // true,false
        return new AudioFormat(AUDIO_ENCODE, sampleRate, sampleSizeInBits, channels, frameSize, sampleRate,  bigEndian);
    }

    private void initFragment() {
        AudioFormat audioFormat = getAudioFormat();
        int numChannels = audioFormat.getChannels();
        int sampleRate = (int) audioFormat.getSampleRate();
        int audioBufferSize = sampleRate * numChannels;
        fragment = new byte[audioBufferSize];
    }

    public class AvData {
        private byte[] data;
        private int length;
        private ByteType byteType;
        private Image image;

        public AvData(ByteType byteType, Image image) {
            this.byteType = byteType;
            this.image = image;
        }

        public AvData(ByteType byteType, byte[] data, int length) {
            this.byteType = byteType;
            this.data = data;
            this.length = length;
        }

        public byte[] getData() {
            return data;
        }

        public int getLength() {
            return length;
        }

        public ByteType getByteType() {
            return byteType;
        }

        public Image getImage() {
            return image;
        }

        public void setImage(Image image) {
            this.image = image;
        }
    }
}
